﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Text;

namespace Cdp
{
    public class CdpListener
    {
        private AsyncUdpClient listener;
        private Dictionary<EndPoint, CdpEndPoint> clients;


        public Int16 MTU;
        public event CdpEventHandler OnAccepeted;
        public event CdpEventHandler OnReceived;
        public event CdpEventHandler OnSent;
        public event CdpEventHandler OnDisconnected;

        public CdpListener(int bufferSize)
        {
            listener = new AsyncUdpClient(bufferSize);
            clients = new Dictionary<EndPoint, CdpEndPoint>();
        }

        public void Listen(IPEndPoint localEndPoint)
        {
            listener.Listen(localEndPoint);
            listener.OnReceived += Listener_OnReceived;
        }

        public void Send(CdpDatagram data)
        {

        }

        public void Stop()
        {

        }

        private void Listener_OnReceived(object sender, CdpDatagram data)
        {
            CdpMessage msg = CdpMessage.Parse(data.Buffer, data.Offset, data.BytesTransferred);
            if(msg != null)
            {
                CdpEndPoint client = clients[data.RemoteEndPoint];
                if (client == null || !client.IsConnected)
                {
                    if (msg.Type == MessageType.Synchronize || msg.IsAcknowledge == false)
                    {
                        msg.SequenceNumber = msg.SequenceNumber > MTU ? MTU : msg.SequenceNumber;
                        if (client == null)
                        {
                            client = new CdpEndPoint()
                            {
                                RemoteEndPoint = data.RemoteEndPoint,
                                IsConnected = true,
                                Generater = new CdpGenerater(),
                                MTU = msg.SequenceNumber,
                                RTT = 0
                            };
                            clients.Add(client.RemoteEndPoint, client);
                        }
                        else
                            client.IsConnected = true;
                        msg.IsAcknowledge = true;
                        if (msg.HasTimeStamp)
                        {
                            msg.TimeStamp = CdpGenerater.GetTimestamp();
                            msg.MicroTimeStamp = CdpGenerater.GetMicroTimestamp();
                        }
                        listener.SendAsync(msg.GenerateCdpDatagram(client.RemoteEndPoint));
                        OnAccepeted?.Invoke(this, data);
                    }
                }
                else
                {
                    switch(msg.Type)
                    {
                        case MessageType.HeartBeat:
                            client.IsConnected = true;
                            break;
                        case MessageType.RoundTripTime:
                            if(msg.IsAcknowledge)
                            {
                                if(msg.HasTimeStamp)
                                {
                                    int second = CdpGenerater.GetTimestamp() - msg.TimeStamp;
                                    int micro = CdpGenerater.GetMicroTimestamp() - msg.MicroTimeStamp;
                                    client.RTT = second * 1000000 + micro;
                                }
                            }
                            else
                            {
                                msg.SequenceNumber = client.Generater.GetPSequence();
                                msg.IsAcknowledge = true;
                                listener.SendAsync(msg.GenerateCdpDatagram(data.RemoteEndPoint));
                            }
                            break;
                        case MessageType.Synchronize:
                            break;
                        case MessageType.Finish:
                            client.IsConnected = false;
                            OnDisconnected?.Invoke(this, data);
                            clients.Remove(client.RemoteEndPoint);
                            break;
                        case MessageType.Pull:
                            break;
                        case MessageType.Push:
                            if (msg.IsFinalMessage)
                            {
                                CdpDatagram var = new CdpDatagram();
                                var.Buffer = msg.Content;
                                var.Offset = 0;
                                var.BytesTransferred = var.Buffer.Length;
                                var.RemoteEndPoint = data.RemoteEndPoint;
                                OnReceived?.Invoke(this, var);
                            }                            
                            break;
                        default:
                            break;
                    }
                }
            }
        }
    }
}
